package org.jugile.util;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import net.minidev.json.JSONObject;
import net.minidev.json.parser.JSONParser;

/*

Copyright (C) 2007-2012 Jukka Rahkonen  email: jukka.rahkonen@iki.fi

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA

*/

/**
 * ""
 * Simple and generic ValueObject. Easy to render json.
 * @author jukka.rahkonen@iki.fi
 */
public class Vo extends Jugile implements java.io.Serializable {

	private List<Item> items = new ArrayList<Item>();
	
	public class Item {
		public String key;
		public Object value;
		public Item(String key) { 
			this.key = key;
		}
		public Item(String key, Object value) {
			this.key = key;
			this.value = value;
		}
	}
	
	private Item getItem(String key) {
		for (Item i : items) if (eq(key,i.key)) return i;
		return null;
	}
	private Object getItemValue(String key) {
		Item i = getItem(key);
		if (i != null) return i.value;
		return null;
	}
	private void setItemValue(String key, Object value) {
		if (empty(key)) return;
		Item i = getItem(key);
		if (i == null) {
			i = new Item(key);
			items.add(i);
		}
		i.value = value;
	}
	private List<String> getKeys() {
		List<String> res = new ArrayList<String>();
		for (Item i : items) res.add(i.key);
		return res;
	}
	
//	private Map<String,Object> flds = null; 
//	public Map<String,Object> flds() {
//		if (flds == null) flds = new HashMap<String,Object>();
//		return flds;
//	}
	
	//public Object get(String key) { return flds().get(key); }
	public Object get(String key) { return getItemValue(key); }
	//public Set<String> keys() { return flds().keySet(); }
	public List<String> keys() { return getKeys(); }
	public List list(String key) { return getOrCreateList(key); }	
	public String str(String key) {
//		Object o = flds().get(key);
		Object o = getItemValue(key);
		if (o == null) return null;
		return o.toString();
	}

	public Vo set(String key, Object value) {
		//flds().put(key, value);
		setItemValue(key, value);
		return this;
	}

	public void add(String listname, Object item) { getOrCreateList(listname).add(item); }
	public void add(String listname) { getOrCreateList(listname); }
	private List<Object> getOrCreateList(String listname) {
		//Object o = flds().get(listname);
		Object o = getItemValue(listname);
		if (o == null) {
			o = new ArrayList<Object>();
			//flds().put(listname, o);
			setItemValue(listname,o);
		} else {
			if (!(o instanceof List)) fail("not a list: " + listname);
		}
		return (List<Object>)o;
	}

	public String toString() { return toJson(); }
	public String toJson() {
		Buffer buf = new Buffer();
		dump(buf);
		return buf.toString();		
	}
	
	public String toJsonLine() {
		Buffer buf = new Buffer();
		buf.setLinemode(true);
		dump(buf);
		return buf.toString();
	}
	
	public String toXml() { return toXml("doc"); }
	public String toXml(String root) {
		Node n = new Node(root);
		dumpXml(n);
		return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" + n.render();
	}
	
	public void dump(Buffer buf) {
		// flds
		buf.ln("{");
		buf.incrIndent();
		int j = 0;
		//for (String key : flds().keySet()) {
		//		if (j++ > 0) buf.insertBeforeLastNewLine(",");
		//		if (get(key) instanceof List) {
		//			dumpList(buf,key,(List<Object>)get(key));
		//		} else if(get(key) instanceof Vo) {
		//			buf.ln(dquote(key) + ": ");
		//			((Vo)get(key)).dump(buf);
		//		} else {
		//			buf.ln(dquote(key) + ": " + toString(get(key)));
		//		}
		//	}
		for (Item i : items) {
			if (j++ > 0) buf.insertBeforeLastNewLine(",");
			if (i.value instanceof List) {
				dumpList(buf,i.key,(List<Object>)i.value);
			} else if(i.value instanceof Vo) {
				buf.ln(dquote(i.key) + ": ");
				((Vo)i.value).dump(buf);
			} else {
				buf.ln(dquote(i.key) + ": " + toString(i.value));
			}
		}
		buf.decrIndent();
		buf.ln("}");
	}
	
	public void dumpList(Buffer buf, String key, List<Object> list) {
		String start = "[";
		if (key != null) start = dquote(key) +": [";
		buf.ln(start);
		buf.incrIndent();
		int i = 0;
		for (Object o : list) {
			if (i++ > 0) buf.insertBeforeLastNewLine(",");
			if (o instanceof Vo) {
				((Vo)o).dump(buf);
			} else if (o instanceof List) {
				dumpList(buf, null, (List<Object>)o);
			} else {
				buf.ln(toString(o));
			}
		}
		buf.decrIndent();
		buf.ln("]");
		
	}
	
	private String dquote(String str) {
		return "\""+str+"\"";
	}

	private String toString(Object o) {
		if (o == null) return "null";
		if (o instanceof Boolean) {
			if (((Boolean)o)) return "true";
			return "false";
		}
		if (o instanceof Integer) return o.toString();
		if (o instanceof Long) return o.toString();
		if (o instanceof Double) return o.toString();
		if (o instanceof Float) return o.toString();
		if (o instanceof Time) return dquote(((Time)o).getW3c());
		String val = replaceControls(o.toString());
		val = escapeUtf8(val);
		return "\""+ val +"\"";
	}

	public void dumpXml(Node n) {
		//for (String key : flds().keySet()) {
		for (String key : keys()) {
			Node n2 = n.add(key);
			if (get(key) instanceof ArrayList) {
				for (Object o : (ArrayList<Object>)get(key)) {
					Node n3 = n2.add("item");
					if (o instanceof Vo) {
						((Vo)o).dumpXml(n3);
					} else {
						n3.value(o);
					}
				}
			} else if(get(key) instanceof Vo) {
				((Vo)get(key)).dumpXml(n2);
			} else {
				n2.value(get(key));
			}
		}
	}

	public static String escapeUtf8(String str) {
		Buffer buf = new Buffer();
		for (int i = 0; i < str.length(); i++) {
			char ch = str.charAt(i);
			if (ch > 126 || ch < 32) buf.add(jsonEscape(ch)); 
//			if (ch == 'ö') { buf.add("\\u00F6"); continue; }
//			if (ch == 'Ö') { buf.add("\\u00D6"); continue; }
//			if (ch == 'ä') { buf.add("\\u00E4"); continue; }
//			if (ch == 'Ä') { buf.add("\\u00C4"); continue; }
//			if (ch == 'å') { buf.add("\\u00E5"); continue; }
//			if (ch == 'Å') { buf.add("\\u00C5"); continue; }
//			if (ch == 0xE9) { buf.add("\\u00E9"); continue; }
//			if (ch == 0xC9) { buf.add("\\u00C9"); continue; }
			else buf.add(ch);
		}
		return buf.toString();
	}
	
	public static String jsonEscape(char ch) {
		String res = "\\u";
		if (ch < 0x1000) res += "0";
		if (ch < 0x100) res += "0";
		if (ch < 0x10) res += "0";
		return res + Integer.toHexString(ch);
	}

	public boolean equals(Vo vo) {
		if (vo == null) return false;
//		if (flds == null && vo.flds == null) return true;
//		if (flds == null || flds == null) return false;
//		if (flds.size() != vo.flds.size()) return false;
		if (items == null && vo.items == null) return true;
		if (items == null || items == null) return false;
		if (items.size() != vo.items.size()) return false;
//		for (String key : flds.keySet()) {
//			Object o1 = flds.get(key);
//			Object o2 = vo.flds.get(key);
		for (String key : keys()) {
			Object o1 = getItemValue(key);
			Object o2 = vo.getItemValue(key);
			if (o1 == null && o2 == null) continue;
			if (o1 == null && o2 != null) return false;
			if (o1 != null && o2 == null) return false;
			if (o1 instanceof List && o2 instanceof List) {
				if (!compareLists((List)o1,(List)o2)) return false; // compare lists
				continue;
			}
			if (o1 instanceof Vo && o2 instanceof Vo) {
				if (!((Vo)o1).equals((Vo)o2)) return false;
				continue;
			}
			if (!o1.getClass().equals(o2.getClass())) return false; 
			if (!o1.equals(o2)) return false;
		}
		return true;
	}
	
	public int hashCode() { return items.hashCode(); }

	private boolean compareLists(List l1, List l2) {
		if (l1.size() != l2.size()) return false;
		for (int i = 0; i < l1.size(); i++) {
			Object o1 = l1.get(i);
			Object o2 = l2.get(i);
			if (o1 == null && o2 == null) continue;
			if (!(o1 != null && o2 != null)) return false;
			if (o1 instanceof Vo && o2 instanceof Vo) {
				if (!((Vo)o1).equals((Vo)o2)) return false;
				continue;
			}
			if (o1 instanceof List && o2 instanceof List) {
				if (!compareLists((List)o1,(List)o2)) return false;
				continue;
			}
			if (!o1.getClass().equals(o2.getClass())) return false; 
			if (!o1.equals(o2)) return false;
		}
		return true;
	}
		
	// -------- parser ----------
	public static Vo parseJsonFile(String filename) {
		Blob b = new Blob(filename);
		return parseJson(b.toString());
	}
	public static Vo parseJson(String json) {
		JSONObject o = null;
		try {
    		JSONParser p = new JSONParser();
    		o = (JSONObject)p.parse(json);		
		} catch (Exception e) { fail(e); }
		return (Vo)parseObject(o);
	}
	
	public static Object parseObject(Object o) {
		if (o instanceof Map) {
			Vo v = new Vo();
			for (Object key : ((Map)o).keySet()) {
				v.set((String)key, parseObject(((Map)o).get(key)));
			}
			return v;
		} 
		if (o instanceof List) {
			List lst = new ArrayList();
			for (Object item : ((List)o)) {
				lst.add(parseObject(item));
			}
			return lst;
		} 
		return o;
	}

}
